﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace CommonUtils
{
    /// <summary>
    /// 文件夹操作
    /// </summary>
    public static class FolderUtil
    {
        public static void MoveFilesToRoot(this DirectoryInfo folder)
        {
            //移动到根目录
            var files = folder.GetSubFoldersAllFiles();
            foreach (var file in files)
                file.MoveToFolder(folder, FileMoveExistsOperation.RenameSource);

            //删除所有子文件夹，理论上全空了
            var folders = folder.GetSubFolders();
            foreach (var subFolder in folders)
                subFolder.DeleteIfNoFile();
        }

        public static void MoveFilesToRoot(string folder)
        => Get(folder).MoveFilesToRoot();

        /// <summary>
        /// 重命名
        /// </summary>
        public static void RenameTo(this DirectoryInfo folder, string name)
        {
            //自身
            if (folder.Name == name)
                return;

            var newPath = folder.Parent.Combine(name);

            //已存在
            if (Exists(newPath))
                return;

            folder.MoveTo(newPath);
        }

        /// <summary>
        /// 获取文件夹
        /// </summary>
        public static DirectoryInfo[] GetFolders(this DirectoryInfo folder)
        => folder.GetDirectories();

        /// <summary>
        /// 获取文件夹
        /// </summary>
        public static DirectoryInfo[] GetFolders(string folder)
        => Get(folder).GetDirectories();

        /// <summary>
        /// 相对路径 不以/开头，不以/结尾
        /// </summary>
        public static string Relative(this DirectoryInfo folder, string path)
        {
            var name = path.Replace('\\', '/').Remove(folder.FullName.Replace('\\', '/'));
            if (name.StartsWith('/'))
                name = name.Substring(1);
            if (!name.EndWith('/'))
                name += '/';
            return name;
        }

        /// <summary>
        /// 相对路径 不以/开头，不以/结尾
        /// </summary>
        public static string GetSubRelativePath(this DirectoryInfo folder, FileSystemInfo file)
        => folder.Relative(file.FullName);

        /// <summary>
        /// 路径拼接
        /// </summary>
        public static string Combine(this DirectoryInfo folder, params string[] right)
        => folder.FullName.Combine(right);

        /// <summary>
        /// 文件夹下的文件
        /// </summary>
        public static FileInfo GetChildFile(this DirectoryInfo folder, string relativePath)
        => FileUtil.GetInfo(folder.Combine(relativePath));

        /// <summary>
        /// 获取子文件夹
        /// </summary>
        public static DirectoryInfo GetChildFolder(this DirectoryInfo folder, string relativePath)
        => GetInfo(folder.Combine(relativePath));

        /// <summary>
        /// 判断文件夹下的子文件或子文件夹是否存在
        /// </summary>
        public static bool Exists(this DirectoryInfo folder, string relativePath)
        => folder.GetChildFile(relativePath).Exists || folder.GetChildFolder(relativePath).Exists;

        /// <summary>
        /// 包含子文件夹
        /// </summary>
        public static FileInfo[] GetAllFiles(this DirectoryInfo folder, List<FileInfo> files = null)
        {
            if (files == null)
                files = new List<FileInfo>();
            files.AddRange(folder.GetFiles());
            foreach (var subFolder in folder.GetSubFolders())
                subFolder.GetAllFiles(files);
            return files.ToArray();
        }

        /// <summary>
        /// 只含子文件夹
        /// </summary>
        public static FileInfo[] GetSubFoldersAllFiles(this DirectoryInfo folder)
        {
            var files = new List<FileInfo>();
            foreach (var subFolder in folder.GetSubFolders())
                subFolder.GetAllFiles(files);
            return files.ToArray();
        }

        /// <summary>
        /// 包含子文件夹
        /// </summary>
        public static FileInfo[] GetAllFiles(string folder)
        => Get(folder).GetAllFiles();

        /// <summary>
        /// 不包含子文件夹(忽略文件夹不存在)
        /// </summary>
        public static FileInfo[] GetFilesEx(this DirectoryInfo folder)
        {
            if (folder.Exists)
                return folder.GetFiles();
            return FileUtil.EmptyInfos;
        }

        /// <summary>
        /// 不包含子文件夹
        /// </summary>
        public static FileInfo[] GetFiles(string path)
        => GetInfo(path).GetFilesEx();

        public static List<DirectoryInfo> GetAllFolders(this DirectoryInfo folder)
        {
            var folders = new List<DirectoryInfo>();
            var temp = folder.GetDirectories();
            folders.AddRange(temp);
            foreach (var subFolder in temp)
                folders.AddRange(subFolder.GetAllFolders());
            return folders;
        }

        public static List<DirectoryInfo> GetAllFolders(string folder)
        => Get(folder).GetAllFolders();

        public static DirectoryInfo[] GetSubFolders(this DirectoryInfo folder)
        => folder.GetFolders();

        public static DirectoryInfo[] GetSubFolders(string folder)
        => Get(folder).GetSubFolders();

        public static bool IsEmpty(this DirectoryInfo folder)
   => folder.GetFileSystemInfos().Length == 0;

        public static bool HasFiles(this DirectoryInfo folder)
        => folder.GetAllFiles().Length > 0;

        public static bool HasNoFile(this DirectoryInfo folder)
        => folder.GetAllFiles().Length == 0;

        /// <summary>
        /// 获取所有空文件夹
        /// </summary>
        public static DirectoryInfo[] GetEmptyFolders(this DirectoryInfo folder)
        {
            var list = new List<DirectoryInfo>();
            foreach (var subFolder in folder.GetDirectories())
                if (subFolder.IsEmpty())
                    list.Add(subFolder);
                else
                    list.AddRange(subFolder.GetEmptyFolders());
            return list.ToArray();
        }

        /// <summary>
        /// 打开文件夹
        /// </summary>
        public static void Open(string path)
        => ProcessUtil.Run(@"C:\Windows\explorer.exe", path.ReplaceSprit(false).AddDoubleQuotes());

        /// <summary>
        /// 打开文件夹并选中文件
        /// </summary>
        public static void OpenFileFolder(string path)
        => ProcessUtil.Run(@"C:\Windows\explorer.exe", "/select," + path.ReplaceSprit(false).AddDoubleQuotes());

        /// <summary>
        /// 创建文件夹
        /// </summary>
        public static void Create(string path)
        => Directory.CreateDirectory(path);

        /// <summary>
        /// 判断是否存在
        /// </summary>
        public static bool Exists(string path)
        => Directory.Exists(path);

        public static bool NotExists(string path)
         => !Directory.Exists(path);

        /// <summary>
        /// 递归删除
        /// </summary>
        public static void DeleteRecursive(string path)
        => Directory.Delete(path, true);

        /// <summary>
        /// 递归删除
        /// </summary>
        public static void DeleteRecursive(this DirectoryInfo folder)
        => folder.Delete(true);

        /// <summary>
        /// 只有空文件夹可以被删除
        /// </summary>
        public static void DeleteIfEmpty(this DirectoryInfo folder)
        {
            try
            {
                folder.Delete();
            }
            catch (Exception ex)
            {
                LogUtil.PrintInfo("try to delete folder error,{},{}", folder.FullName, ex.Message);
            }
        }

        /// <summary>
        /// 删除空文件夹(包括含有子文件夹不含子文件的情况)
        /// </summary>
        public static void DeleteIfNoFile(string path)
        => Get(path).DeleteIfNoFile();

        /// <summary>
        /// 删除空文件夹(包括含有子文件夹不含子文件的情况)
        /// </summary>
        public static void DeleteIfNoFile(this DirectoryInfo folder)
        {
            LogUtil.Info("尝试删除空文件夹:\r\n" + folder);
            if (folder.HasNoFile())
                folder.DeleteRecursive();
        }

        /// <summary>
        /// 获取文件夹信息
        /// </summary>
        public static DirectoryInfo GetInfo(string path)
        {
            if (path.IsNullOrEmpty())
                return null;
            return new DirectoryInfo(path);
        }

        /// <summary>
        /// 获取文件夹信息
        /// </summary>
        public static DirectoryInfo Get(string path)
        => GetInfo(path);

        /// <summary>
        /// 文件夹中的文件最后修改时间
        /// </summary>
        /// <param name="sub">包含子文件夹</param>
        public static DateTime LastFileWriteTime(this DirectoryInfo folder, bool sub = true)
        {
            DateTime time = DateTime.MinValue;
            foreach (var file in folder.GetFiles())
                if (file.LastWriteTime > time)
                    time = file.LastWriteTime;
            if (sub)
            {
                foreach (var subFolder in folder.GetDirectories())
                {
                    var subTime = subFolder.LastFileWriteTime(sub);
                    if (subTime > time)
                        time = subTime;
                }
            }
            return time;
        }

        /// <summary>
        /// 复制文件夹
        /// </summary>
        /// <param name="sub">包含子文件夹</param>
        public static void CopyTo(this DirectoryInfo src, DirectoryInfo dest, bool sub = true, bool overwrite = true, params string[] ignores)
        {
            if (src.IsNullOrNotExists())
                return;

            //创建文件夹，兼容了空文件夹
            dest.Create();

            foreach (var file in src.GetFiles())
            {
                if (ignores.Contains(file.Name))
                    continue;
                file.CopyTo(dest.GetChildFile(file.Name), overwrite);
            }

            if (!sub)
                return;

            foreach (var folder in src.GetDirectories())
            {
                if (ignores.Contains(folder.Name))
                    continue;
                folder.CopyTo(dest.GetChildFolder(folder.Name), sub, overwrite, ignores);
            }
        }

        /// <summary>
        /// 复制文件夹
        /// </summary>
        /// <param name="sub">包含子文件夹</param>
        public static void CopyTo(this DirectoryInfo src, string dest, bool sub = true, bool overwrite = true)
        => src.CopyTo(GetInfo(dest), sub, overwrite);

        /// <summary>
        /// 复制文件夹
        /// </summary>
        /// <param name="sub">包含子文件夹</param>
        public static void Copy(string src, string dest, bool sub = true, bool overwrite = true)
        => GetInfo(src).CopyTo(dest, sub, overwrite);

        /// <summary>
        /// 移动、覆盖、合并文件夹
        /// 虽然和复制很像，但确实不是复制，文件进行的移动操作，比复制效率高
        /// </summary>
        public static void MoveToEx(this DirectoryInfo src, string dest)
        {
            //src.MoveTo(dest);不支持合并覆盖，只支持移动

            if (src.IsNullOrNotExists())
                return;

            //创建文件夹，兼容了空文件夹
            Create(dest);

            foreach (var file in src.GetFiles())
                file.FixFolderAndMoveTo(Path.Combine(dest, file.Name));

            foreach (var folder in src.GetDirectories())
                folder.MoveToEx(Path.Combine(dest, folder.Name));
        }

        /// <summary>
        /// 移动、覆盖、合并文件夹
        /// </summary>
        public static void Move(string from, string to)
        => GetInfo(from).MoveToEx(to);

        /// <summary>
        /// 删除不对称的文件
        /// </summary>
        /// <param name="sub">包含子文件夹</param>
        public static void RemoveNotDoubleExistsFiles(this DirectoryInfo src, DirectoryInfo dest, bool sub = true, params string[] ignores)
        {
            if (src.IsNullOrNotExists() || dest.IsNullOrNotExists())
                return;

            foreach (var file in dest.GetFiles())
            {
                if (ignores.Contains(file.Name))
                    continue;
                if (!src.Exists(dest.GetSubRelativePath(file)))
                    file.Delete();
            }

            if (!sub)
                return;

            foreach (var folder in dest.GetFolders())
            {
                if (ignores.Contains(folder.Name))
                    continue;
                if (!src.Exists(dest.GetSubRelativePath(folder)))
                    folder.DeleteRecursive();
                else
                    RemoveNotDoubleExistsFiles(src.GetChildFolder(dest.GetSubRelativePath(folder)), folder, sub);
            }
        }
    }
}
